Skip to content

feat(query-db): support eager initial data#1683

Merged
KyleAMathews merged 5 commits into
mainfrom
query-initial-placeholder-data-design
Jul 20, 2026
Merged

feat(query-db): support eager initial data#1683
KyleAMathews merged 5 commits into
mainfrom
query-initial-placeholder-data-design

Conversation

@KyleAMathews

@KyleAMathews KyleAMathews commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Adds typed, collection-local initialData and initialDataUpdatedAt support to eager Query Collections. Initial Query responses now materialize immediately as collection rows while retaining TanStack Query's cache identity, freshness, and wrapped-response semantics.

Root Cause

Query Collections already materialized data preloaded into a QueryClient, but they could not declare initial data per collection. Applications sharing one client across many collections therefore had to choose between overly broad client defaults and imperative cache seeding. Forwarding defaults unchanged was also unsafe for on-demand subsets and placeholder data: neither has collection-wide row-membership semantics.

Approach

  • Add typed top-level initialData and initialDataUpdatedAt options using the original Query response type.
  • Forward those options only for eager observers, then reuse the existing select, materialization, and row-ownership pipeline.
  • Reject explicit collection-level initial data in on-demand mode and suppress client-wide initial-data defaults for derived subset observers.
  • Suppress placeholderData defaults because placeholder results are observer-local UI state, not Query cache data suitable for normalized rows.
  • Document the authority, lifecycle, wrapped-response, write, ownership, and persistence semantics.

Key Invariants

  • The Query cache remains authoritative; existing cached or hydrated data wins over a later initializer.
  • Exact Query keys identify shared documents, even when collections share a QueryClient.
  • select materializes rows without replacing the original wrapped response in the Query cache.
  • Stale initial rows remain available during refetch and reconcile through normal ownership rules.
  • Placeholder data and collection-wide initializers never become rows for arbitrary on-demand subsets.
  • Persistence needs no new format or temporary-row metadata.

Non-goals

  • Materializing placeholderData as DB rows.
  • Defining a subset-aware initializer for on-demand collections.
  • Giving each collection a private cache document when it uses the same exact Query key.
  • Changing direct-write or persistence formats.

Trade-offs

On-demand mode rejects explicit initial-data options rather than guessing subset membership. Callers that know an exact derived key can still seed or hydrate that Query cache entry. Eager mode suppresses placeholder defaults to keep observer presentation state out of the collection's shared normalized state.

Verification

pnpm vitest run packages/query-db-collection/tests/query.test.ts --maxWorkers=1
pnpm --filter @tanstack/query-db-collection build
pnpm --filter @tanstack/query-db-collection lint
git diff --check

The focused Query Collection suite covers fresh and stale initial data, function initializers, wrapped response projection, shared clients and exact keys, existing-cache precedence, on-demand rejection/default suppression, and placeholder-default suppression. Lint passes with existing warnings.

Files changed

  • packages/query-db-collection/src/query.ts — adds the public options and observer initialization policy.
  • packages/query-db-collection/src/errors.ts — adds the on-demand configuration error.
  • packages/query-db-collection/tests/query.test.ts — adds initialization and isolation coverage.
  • docs/collections/query-collection.md — documents usage and user-visible semantics.
  • docs/collections/query-initial-placeholder-data-design.md — records authority, ownership, transitions, writes, and persistence decisions.
  • .changeset/young-cats-initialize.md — records the minor package feature.

Closes #346. Advances #1643.

Summary by CodeRabbit

  • New Features

    • Added eager collection support for TanStack Query’s initialData and initialDataUpdatedAt.
    • Initial data is immediately available as collection rows while preserving wrapped response data.
    • Fresh initial data can prevent an unnecessary fetch; stale data is refreshed and reconciled with server results.
    • Added safeguards so placeholder data is not materialized as collection rows.
    • Added clear errors when initial data is used with on-demand collections.
  • Documentation

    • Documented initial data behavior, cache precedence, wrapped responses, and placeholder data limitations.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 711bbc83-cf04-4fad-b8da-eac96f612650

📥 Commits

Reviewing files that changed from the base of the PR and between 5b3fd1d and b187e6b.

📒 Files selected for processing (4)
  • .changeset/young-cats-initialize.md
  • docs/collections/query-initial-placeholder-data-design.md
  • packages/query-db-collection/src/query.ts
  • packages/query-db-collection/tests/query.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • .changeset/young-cats-initialize.md
  • docs/collections/query-initial-placeholder-data-design.md

📝 Walkthrough

Walkthrough

Query Collections now support eager initialData and initialDataUpdatedAt, including wrapped-response projection through select. On-demand collections reject collection-level initializers, while placeholder data is prevented from becoming normalized collection rows.

Changes

Eager initial data support

Layer / File(s) Summary
Initial data and placeholder semantics
docs/collections/query-initial-placeholder-data-design.md, docs/collections/query-collection.md
Documents eager initialization, cache precedence and freshness, wrapped-response projection, lifecycle behavior, on-demand limitations, and unsupported placeholder materialization.
Runtime wiring and revalidation
packages/query-db-collection/src/errors.ts, packages/query-db-collection/src/query.ts
Adds typed initializer options and an on-demand error, configures observer behavior for eager and on-demand modes, suppresses placeholder data, and ensures seeded data does not satisfy retained-query revalidation.
Behavioral coverage and release metadata
packages/query-db-collection/tests/query.test.ts, .changeset/young-cats-initialize.md
Tests initialization, reconciliation, projection, cache sharing, cleanup, defaults, and persisted revalidation; records the package minor release.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant QueryCollection
  participant QueryObserver
  participant QueryClient
  QueryCollection->>QueryObserver: configure initialData and select
  QueryObserver->>QueryClient: read or seed query cache
  QueryClient-->>QueryObserver: response and freshness state
  QueryObserver-->>QueryCollection: materialized projected rows
Loading

Possibly related PRs

  • TanStack/db#1654: Extends related eager initial-data and wrapped-response projection behavior.
  • TanStack/db#1665: Modifies the same Query Collection observer-option wiring.
  • TanStack/db#1682: Updates related initial-data and placeholder-data semantics documentation.

Suggested reviewers: kevin-dp

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it doesn't follow the repo template sections for Changes, Checklist, and Release Impact. Reformat the PR description to use the required headings: 🎯 Changes, ✅ Checklist, and 🚀 Release Impact, and include the checklist items.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: eager initial data support for query-db collection.
Linked Issues check ✅ Passed The PR implements the requested eager initialData/initialDataUpdatedAt support, staleness behavior, and placeholder suppression for #346.
Out of Scope Changes check ✅ Passed The changes stay focused on eager initial data support, docs, tests, and the related changeset/design note; no clear unrelated code is introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch query-initial-placeholder-data-design

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 16, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-db

npm i https://pkg.pr.new/@tanstack/angular-db@1683

@tanstack/browser-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/browser-db-sqlite-persistence@1683

@tanstack/capacitor-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/capacitor-db-sqlite-persistence@1683

@tanstack/cloudflare-durable-objects-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/cloudflare-durable-objects-db-sqlite-persistence@1683

@tanstack/db

npm i https://pkg.pr.new/@tanstack/db@1683

@tanstack/db-ivm

npm i https://pkg.pr.new/@tanstack/db-ivm@1683

@tanstack/db-sqlite-persistence-core

npm i https://pkg.pr.new/@tanstack/db-sqlite-persistence-core@1683

@tanstack/electric-db-collection

npm i https://pkg.pr.new/@tanstack/electric-db-collection@1683

@tanstack/electron-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/electron-db-sqlite-persistence@1683

@tanstack/expo-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/expo-db-sqlite-persistence@1683

@tanstack/node-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/node-db-sqlite-persistence@1683

@tanstack/offline-transactions

npm i https://pkg.pr.new/@tanstack/offline-transactions@1683

@tanstack/powersync-db-collection

npm i https://pkg.pr.new/@tanstack/powersync-db-collection@1683

@tanstack/query-db-collection

npm i https://pkg.pr.new/@tanstack/query-db-collection@1683

@tanstack/react-db

npm i https://pkg.pr.new/@tanstack/react-db@1683

@tanstack/react-native-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/react-native-db-sqlite-persistence@1683

@tanstack/rxdb-db-collection

npm i https://pkg.pr.new/@tanstack/rxdb-db-collection@1683

@tanstack/solid-db

npm i https://pkg.pr.new/@tanstack/solid-db@1683

@tanstack/svelte-db

npm i https://pkg.pr.new/@tanstack/svelte-db@1683

@tanstack/tauri-db-sqlite-persistence

npm i https://pkg.pr.new/@tanstack/tauri-db-sqlite-persistence@1683

@tanstack/trailbase-db-collection

npm i https://pkg.pr.new/@tanstack/trailbase-db-collection@1683

@tanstack/vue-db

npm i https://pkg.pr.new/@tanstack/vue-db@1683

commit: b187e6b

@github-actions

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 125 kB

ℹ️ View Unchanged
Filename Size
packages/db/dist/esm/collection/change-events.js 1.43 kB
packages/db/dist/esm/collection/changes.js 1.38 kB
packages/db/dist/esm/collection/cleanup-queue.js 810 B
packages/db/dist/esm/collection/events.js 434 B
packages/db/dist/esm/collection/index.js 3.62 kB
packages/db/dist/esm/collection/indexes.js 1.99 kB
packages/db/dist/esm/collection/lifecycle.js 1.69 kB
packages/db/dist/esm/collection/mutations.js 2.47 kB
packages/db/dist/esm/collection/state.js 5.48 kB
packages/db/dist/esm/collection/subscription.js 3.74 kB
packages/db/dist/esm/collection/sync.js 2.88 kB
packages/db/dist/esm/collection/transaction-metadata.js 144 B
packages/db/dist/esm/deferred.js 207 B
packages/db/dist/esm/errors.js 5.1 kB
packages/db/dist/esm/event-emitter.js 748 B
packages/db/dist/esm/index.js 3.16 kB
packages/db/dist/esm/indexes/auto-index.js 829 B
packages/db/dist/esm/indexes/base-index.js 767 B
packages/db/dist/esm/indexes/basic-index.js 2.06 kB
packages/db/dist/esm/indexes/btree-index.js 2.19 kB
packages/db/dist/esm/indexes/index-registry.js 820 B
packages/db/dist/esm/indexes/reverse-index.js 557 B
packages/db/dist/esm/live-query-adapter.js 318 B
packages/db/dist/esm/local-only.js 916 B
packages/db/dist/esm/local-storage.js 2.12 kB
packages/db/dist/esm/optimistic-action.js 359 B
packages/db/dist/esm/paced-mutations.js 496 B
packages/db/dist/esm/proxy.js 3.75 kB
packages/db/dist/esm/query/builder/functions.js 1.47 kB
packages/db/dist/esm/query/builder/index.js 5.84 kB
packages/db/dist/esm/query/builder/ref-proxy.js 1.24 kB
packages/db/dist/esm/query/compiler/evaluators.js 1.89 kB
packages/db/dist/esm/query/compiler/expressions.js 430 B
packages/db/dist/esm/query/compiler/group-by.js 3.56 kB
packages/db/dist/esm/query/compiler/index.js 6.67 kB
packages/db/dist/esm/query/compiler/joins.js 2.5 kB
packages/db/dist/esm/query/compiler/lazy-targets.js 923 B
packages/db/dist/esm/query/compiler/order-by.js 1.74 kB
packages/db/dist/esm/query/compiler/select.js 1.53 kB
packages/db/dist/esm/query/effect.js 4.77 kB
packages/db/dist/esm/query/expression-helpers.js 1.43 kB
packages/db/dist/esm/query/ir.js 1.25 kB
packages/db/dist/esm/query/live-query-collection.js 360 B
packages/db/dist/esm/query/live/collection-config-builder.js 9.1 kB
packages/db/dist/esm/query/live/collection-registry.js 264 B
packages/db/dist/esm/query/live/collection-subscriber.js 1.93 kB
packages/db/dist/esm/query/live/internal.js 145 B
packages/db/dist/esm/query/live/utils.js 1.81 kB
packages/db/dist/esm/query/optimizer.js 2.92 kB
packages/db/dist/esm/query/predicate-utils.js 2.97 kB
packages/db/dist/esm/query/query-once.js 359 B
packages/db/dist/esm/query/subset-dedupe.js 960 B
packages/db/dist/esm/scheduler.js 1.3 kB
packages/db/dist/esm/SortedMap.js 1.3 kB
packages/db/dist/esm/strategies/debounceStrategy.js 247 B
packages/db/dist/esm/strategies/queueStrategy.js 428 B
packages/db/dist/esm/strategies/throttleStrategy.js 246 B
packages/db/dist/esm/transactions.js 3.04 kB
packages/db/dist/esm/utils.js 927 B
packages/db/dist/esm/utils/array-utils.js 273 B
packages/db/dist/esm/utils/browser-polyfills.js 304 B
packages/db/dist/esm/utils/btree.js 5.61 kB
packages/db/dist/esm/utils/comparison.js 1.11 kB
packages/db/dist/esm/utils/cursor.js 457 B
packages/db/dist/esm/utils/index-optimization.js 2.39 kB
packages/db/dist/esm/utils/type-guards.js 157 B
packages/db/dist/esm/utils/uuid.js 449 B
packages/db/dist/esm/virtual-props.js 360 B

compressed-size-action::db-package-size

@github-actions

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 4.22 kB

ℹ️ View Unchanged
Filename Size
packages/react-db/dist/esm/index.js 249 B
packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.32 kB
packages/react-db/dist/esm/useLiveQuery.js 1.33 kB
packages/react-db/dist/esm/useLiveQueryEffect.js 355 B
packages/react-db/dist/esm/useLiveSuspenseQuery.js 567 B
packages/react-db/dist/esm/usePacedMutations.js 401 B

compressed-size-action::react-db-package-size

@github-actions

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 125 kB

ℹ️ View Unchanged
Filename Size
packages/db/dist/esm/collection/change-events.js 1.43 kB
packages/db/dist/esm/collection/changes.js 1.38 kB
packages/db/dist/esm/collection/cleanup-queue.js 810 B
packages/db/dist/esm/collection/events.js 434 B
packages/db/dist/esm/collection/index.js 3.62 kB
packages/db/dist/esm/collection/indexes.js 1.99 kB
packages/db/dist/esm/collection/lifecycle.js 1.69 kB
packages/db/dist/esm/collection/mutations.js 2.47 kB
packages/db/dist/esm/collection/state.js 5.48 kB
packages/db/dist/esm/collection/subscription.js 3.74 kB
packages/db/dist/esm/collection/sync.js 2.88 kB
packages/db/dist/esm/collection/transaction-metadata.js 144 B
packages/db/dist/esm/deferred.js 207 B
packages/db/dist/esm/errors.js 5.1 kB
packages/db/dist/esm/event-emitter.js 748 B
packages/db/dist/esm/index.js 3.16 kB
packages/db/dist/esm/indexes/auto-index.js 829 B
packages/db/dist/esm/indexes/base-index.js 767 B
packages/db/dist/esm/indexes/basic-index.js 2.06 kB
packages/db/dist/esm/indexes/btree-index.js 2.19 kB
packages/db/dist/esm/indexes/index-registry.js 820 B
packages/db/dist/esm/indexes/reverse-index.js 557 B
packages/db/dist/esm/live-query-adapter.js 318 B
packages/db/dist/esm/local-only.js 916 B
packages/db/dist/esm/local-storage.js 2.12 kB
packages/db/dist/esm/optimistic-action.js 359 B
packages/db/dist/esm/paced-mutations.js 496 B
packages/db/dist/esm/proxy.js 3.75 kB
packages/db/dist/esm/query/builder/functions.js 1.47 kB
packages/db/dist/esm/query/builder/index.js 5.84 kB
packages/db/dist/esm/query/builder/ref-proxy.js 1.24 kB
packages/db/dist/esm/query/compiler/evaluators.js 1.89 kB
packages/db/dist/esm/query/compiler/expressions.js 430 B
packages/db/dist/esm/query/compiler/group-by.js 3.56 kB
packages/db/dist/esm/query/compiler/index.js 6.67 kB
packages/db/dist/esm/query/compiler/joins.js 2.5 kB
packages/db/dist/esm/query/compiler/lazy-targets.js 923 B
packages/db/dist/esm/query/compiler/order-by.js 1.74 kB
packages/db/dist/esm/query/compiler/select.js 1.53 kB
packages/db/dist/esm/query/effect.js 4.77 kB
packages/db/dist/esm/query/expression-helpers.js 1.43 kB
packages/db/dist/esm/query/ir.js 1.25 kB
packages/db/dist/esm/query/live-query-collection.js 360 B
packages/db/dist/esm/query/live/collection-config-builder.js 9.1 kB
packages/db/dist/esm/query/live/collection-registry.js 264 B
packages/db/dist/esm/query/live/collection-subscriber.js 1.93 kB
packages/db/dist/esm/query/live/internal.js 145 B
packages/db/dist/esm/query/live/utils.js 1.81 kB
packages/db/dist/esm/query/optimizer.js 2.92 kB
packages/db/dist/esm/query/predicate-utils.js 2.97 kB
packages/db/dist/esm/query/query-once.js 359 B
packages/db/dist/esm/query/subset-dedupe.js 960 B
packages/db/dist/esm/scheduler.js 1.3 kB
packages/db/dist/esm/SortedMap.js 1.3 kB
packages/db/dist/esm/strategies/debounceStrategy.js 247 B
packages/db/dist/esm/strategies/queueStrategy.js 428 B
packages/db/dist/esm/strategies/throttleStrategy.js 246 B
packages/db/dist/esm/transactions.js 3.04 kB
packages/db/dist/esm/utils.js 927 B
packages/db/dist/esm/utils/array-utils.js 273 B
packages/db/dist/esm/utils/browser-polyfills.js 304 B
packages/db/dist/esm/utils/btree.js 5.61 kB
packages/db/dist/esm/utils/comparison.js 1.11 kB
packages/db/dist/esm/utils/cursor.js 457 B
packages/db/dist/esm/utils/index-optimization.js 2.39 kB
packages/db/dist/esm/utils/type-guards.js 157 B
packages/db/dist/esm/utils/uuid.js 449 B
packages/db/dist/esm/virtual-props.js 360 B

compressed-size-action::db-package-size

@github-actions

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 4.22 kB

ℹ️ View Unchanged
Filename Size
packages/react-db/dist/esm/index.js 249 B
packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.32 kB
packages/react-db/dist/esm/useLiveQuery.js 1.33 kB
packages/react-db/dist/esm/useLiveQueryEffect.js 355 B
packages/react-db/dist/esm/useLiveSuspenseQuery.js 567 B
packages/react-db/dist/esm/usePacedMutations.js 401 B

compressed-size-action::react-db-package-size

@kevin-dp

Copy link
Copy Markdown
Contributor

@KyleAMathews i had Fable review the PR and chatted through it with Fable. This is what we landed on:

Verdict: approve the architecture; a few things worth addressing before merge — one real risk (persistence interplay), one changeset omission, and some test gaps relative to the PR's own design doc. CI is fully green (unit, E2E, example build, lint/autofix).

Issue 1 — persistence interplay can clobber newer persisted rows (main risk)

On restart with SQLite persistence, the Query cache is empty but persisted rows exist under until-revalidated retention (retainedQueriesPendingRevalidation). With initialData configured:

  1. Observer creation seeds the cache; subscribeToQuery immediately fires a success result.
  2. handleQueryResult sees the pending-revalidation flag and calls reconcileSuccessfulResult (query.ts:1534), which reconciles the persisted baseline against the static seed — deleting persisted rows the seed omits and clearing the retention flag (query.ts:1494).
  3. Crucially, if initialDataUpdatedAt is omitted, query-core stamps dataUpdatedAt = Date.now(), so the seed always looks fresh and no refetch follows for the duration of staleTime.

So a retention mechanism meaning "keep these rows until the server revalidates them" is now satisfiable by static config data with no network involved, silently regressing the user's last-seen state. Mechanically it's "same as a stale server response," but there's no server in the loop. Suggestions:

  • At minimum: a persistence + initialData test (the design doc's own step 5 promises exactly this) and a docs warning to always set initialDataUpdatedAt when persistence is enabled.
  • Worth considering: when a retained query's success result was never fetched (query.state.dataUpdateCount === 0 / isFetchedAfterMount === false), trigger a revalidating refetch instead of treating the seed as revalidation.

Issue 2 — changeset omits two silent behavior changes

The changeset only advertises the new feature, but the PR also changes existing behavior for collections that never opt in:

  • Client-default placeholderData no longer materializes (for all query collections).
  • Client-default initialData no longer seeds on-demand subset observers.

Both are deliberate correctness fixes, but they remove rows an app may currently be (mis)relying on. They should be called out in the changeset so the release notes warn anyone affected.

Issue 3 — test coverage vs. the PR's own claims

Covered well: fresh/stale seeds, function-initializer-evaluated-once, wrapped envelope preserved in cache, shared-client scoping, existing-cache precedence, on-demand rejection and default suppression, placeholder suppression.

Missing relative to the design doc's behavior table and its own implementation sequence (steps 3–5):

  • Refetch error retains initial rows — explicitly promised in the docs ("an error retains the initial rows") but untested; cheap to add.
  • Direct writes (writeInsert/writeUpdate) against a wrapped initial envelope.
  • Cleanup/late-notification and the persistence scenarios above.

Given that, "Closes #346" is a stretch by the design doc's own sequencing ("close or narrow #346 only after these contracts ship" — step 6). Suggest "Advances #346" unless the error-retention and persistence tests land in this PR.

Minor

@KyleAMathews
KyleAMathews merged commit f42db5c into main Jul 20, 2026
10 of 11 checks passed
@KyleAMathews
KyleAMathews deleted the query-initial-placeholder-data-design branch July 20, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Initial Data and Query Options Support to query-db-collection

2 participants